/*
 * MergingSegment.cpp
 *
 *  Created on: Feb 23, 2012
 *      Author: hxu
 */

#include "MergingSegment.h"
#include<algorithm>
#include<sstream>
#include "Toolset.h"
#include "Wire.h"
#include "Edge.h"
#include "TRR.h"
#include "Btree.h"
#include <cmath>


using namespace std;

namespace Cxuh {

void MergingSegment::initialization(){
	// used by all constructors
	xl = 0;
	yl = 0;
	xr = 0;
	yr = 0;
	z = 0;
	node = NULL;
	slope = 0;
	l = -1;
	dmax = 0;
	Cin = 0;

	pointl.setXYZ(xl, yl, z);
	pointr.setXYZ(xr, yr, z);
}

MergingSegment::MergingSegment(){
	// for automatically generated ms
	initialization();
}

MergingSegment::MergingSegment(Cxuh::BtreeNode *nd){
	// generate a ms for a sink
	if (!nd)
		throw(" Error: MergingSegment::init_from_node(): Initialize a merging segment with a NULL!");

	initialization();

	xl = nd->getX();
	yl = nd->getY();
	z = nd->getZ();

	xr = xl;
	yr = yl;
	node = nd;

	l = 0;
	slope = 0;

	pointl.setXYZ(xl, yl, z);
	pointr.setXYZ(xr, yr, z);

	Cin = nd->getC();
}

//MergingSegment::MergingSegment(int xleft, int yleft, int xright, int yright,
//		int zz) :
//		xl(xleft), yl(yleft), xr(xright), yr(yright), z(zz), node(NULL) {
//
//	if (xl > xr) {
//		// xl and xr need to be exchanged
//		swap(xl, xr);
//		swap(yl, yr);
//	}
//
//	l = Txuh::get_manh_dist(xl, yl, xr, yr);
//
//	if (yl < yr)
//		slope = 1;
//	else if (yl > yr)
//		slope = -1;
//	else if (xl == xr)
//		// ms is a point
//		slope = 0;
//	else {
//		stringstream err;
//		err << " Error: Invalid merging segment: (" << xl << ", " << yl
//				<< "), (" << xr << ", " << yr << ")!" << endl;
//		throw(err.str());
//	}
//
//	pointl.setXYZ(xl, yl, z);
//	pointr.setXYZ(xr, yr, z);
//}

MergingSegment::MergingSegment(BtreeNode *nd, MergingSegment *lms,
		MergingSegment *rms) {
	// merge two segments, whose parent is nd
	if (!nd || !lms || !rms)
		throw(" Error: Merging NULL segments!");

	TRR *trr_l, *trr_r;
	int el, er; // distance between "this" and lms, rms

	node = nd;

	// merge two segments
	det_size_of_TRR(lms, rms, el, er); // Cin and dmax are also updated
	trr_l = new TRR(lms, el);
	trr_r = new TRR(rms, er);

	//mergeTRR(trr_l, trr_r); // points and slope are updated
	Edge temp_e = trr_l->merge(*trr_r);
	float temp_slope = temp_e.get_slope();

	if (temp_slope != 0 && temp_slope != 1 && temp_slope != -1) {
		stringstream err_ss;
		err_ss
				<< " Error: MergingSegment::mergeTRR(): the slope of the generated edge is "
				<< temp_e.get_slope() << "!" << endl;
		throw(err_ss.str());
	} else
		slope = temp_slope;

	set_left_point(temp_e.get_pt_left());
	set_right_point(temp_e.get_pt_right());

}

void MergingSegment::det_size_of_TRR(MergingSegment* lms, MergingSegment* rms, int& el, int& er) {
	// determine the size of TRR for lms and rms, update Cin and dmax
	if (!lms || !rms)
		throw(" Error: MergingSegment::det_sizeof_TRR(): Merging NULL segments!");

	float l = lms->getDistance(rms);

	// max delay after l and r children
	int dl = lms->dmax;
	int dr = rms->dmax;

	double ddelta = dl - dr;
	//double ddelta_wire = Btree::ELMORE_DISTR* Wire::R * Wire::C * pow(l,2); // largest delta d provided by the whole wire
	double r = Wire::R, c = Wire::C;
	double Cl = lms->Cin;
	double Cr = rms->Cin;

	el = (Btree::ELMORE_DISTR*r*c*pow(l,2) + Btree::ELMORE_LUMP*r*l*Cr - ddelta) /
		(2*Btree::ELMORE_DISTR*r*c*l + Btree::ELMORE_LUMP* r * (Cl+Cr)); // to obtained 0-skew
	er = l - el;

	// TODO: without any buffer or detour, there will be intrinsic skew
	if(el<0){
		el = 0;
		er = l;
	}
	else if(er<0){
		er = 0;
		dl = l;
	}

	dmax = max(dl, dr);
	Cin = lms->Cin + rms->Cin + Wire::C*(el+er);
}

int MergingSegment::getDistance(MergingSegment *ms) {
	// get the min distance between current ms and the given ms
	int d = 0, dtemp;
	Point<int> pt0, pt1, pt2, pt3;

	// determine the closest points
	d = get_distance_comp_points(ms, pt0, pt3);
	if (d < 0)
		throw(" Error: negative distance between two merging segments!");

	if (this->intersect(ms))
		d = 0; // two ms intersect
	else {
		d = ms->distance_between_pt(pt0);
		dtemp = this->distance_between_pt(pt3);
		d = min(d, dtemp);
	}

	return d;
}

bool MergingSegment::intersect(MergingSegment* ms) const {
	//determine whether this intersects with ms
	bool intersected = false;

	if (!ms)
		throw(" Error: null ms for MergingSegment::intersect()!");

	int slope2 = ms->slope;
	int xl2 = ms->xl;
	int yl2 = ms->yl;
	int xr2 = ms->xr;
	//int yr2 = ms->yr;
	int b2 = yl2 - slope2 * xl2; // y = slope*x + b, eq. for ms
	int b = yl - slope * xl; // eq. for this

	if (slope == 0 && slope2 == 0) {
		// two points
		if ((xl2 = xl) && (yl2 == yl))
			// two points overlap
			intersected = true;
		else
			intersected = false;
	} else if (slope == 0) {
		// "this" is a point
		int ytemp = slope2 * xl + b2;
		if (ytemp == yl)
			intersected = true;
		else
			intersected = false;
	} else if (slope2 == 0) {
		// "ms" is a point
		int ytemp = slope * xl2 + b;
		if (ytemp == yl2)
			intersected = true;
		else
			intersected = false;
	} else if (slope == slope2) {
		if (b != b2)
			// parallel ms
			intersected = false;
		else {
			if ((xl <= xr2 && xr2 <= xr) || (xl <= xl2 && xl2 <= xr))
				// overlapped
				intersected = true;
			else
				intersected = false;
		}
	} else {
		// non-parallel, find the crossing point
		double ytemp = (b + b2) / 2.0; // in case a float (x,y)
		double xtemp = (ytemp - b2) / slope2;

		if (xtemp >= xl && xtemp <= xr && xtemp >= xl2 && xtemp <= xr2)
			intersected = true;
		else
			intersected = false;
	}

	return intersected;
}

int MergingSegment::distance_between_pt(const Point<int>& pt) const {
	// get the distance between a pt and this ms
	int d;
	int quad_index1, quad_index2;
	Point<int> pt1, pt2;

	pt1 = pointl;
	pt2 = pointr;
	quad_index1 = pt.det_quadrant(pt1);
	quad_index2 = pt.det_quadrant(pt2);

	switch (slope) {
	case 1:
		if (quad_index1 % 2 == 0) {
			// pt1 in 2nd or 4th quadrants
			d = pt - pt1;
		} else if (quad_index2 % 2 == 0) {
			// pt2 in 2nd or 4th quadrants
			d = pt - pt2;
		} else if (quad_index1 != quad_index2) {
			// ms spans three quadrants
			d = abs(pt1.getY() - pt.getY() - (pt1.getX() - pt.getX()));
		} else {
			//ms in first or third quadrant
			d = min(pt - pt1, pt - pt2);
		}
		break;

	case -1:
		if (quad_index1 % 2 != 0) {
			// pt1 in 1st or 3rd quadrants
			d = pt - pt1;
		} else if (quad_index2 % 2 != 0) {
			// pt2 in 1 or 3 quadrants
			d = pt - pt2;
		} else if (quad_index1 != quad_index2) {
			// ms spans three quadrants
			d = abs(pt1.getY() - pt.getY() + (pt1.getX() - pt.getX()));
		} else {
			//ms in 2 or 4 quadrant
			d = min(pt - pt1, pt - pt2);
		}
		break;

	case 0:
		// this ms is a point
		d = pt - pt1;
		break;
	default:
		throw(" Error: Wrong slope of ms in MergingSegment::distance_between_pt!");
	}

	return d;
}

int MergingSegment::get_distance_comp_points(MergingSegment *ms,
		Point<int>& pt0, Point<int>& pt2) {
	// get the closest two points compared between this and ms, pt0(x0, y0) belongs to this
	int d = -1, x0 = -1, y0 = -1, z0 = -1, x2 = -1, y2 = -1, z2 = -1;
	d = get_distance_comp_points(ms, x0, y0, z0, x2, y2, z2);
	pt0.setXYZ(x0, y0, z0);
	pt2.setXYZ(x2, y2, z2);

	return d;
}

int MergingSegment::get_distance_comp_points(MergingSegment *ms, int& x0,
		int& y0, int& z0, int& x2, int& y2, int& z2) {
	// get the closest two points compared between this and ms, (x0, y0) belongs to this
	int d = -1, l1, l2, l3, l4;
	int xl2 = ms->xl;
	int yl2 = ms->yl;
	int xr2 = ms->xr;
	int yr2 = ms->yr;

	l1 = Txuh::get_manh_dist(xl, yl, xl2, yl2);
	l2 = Txuh::get_manh_dist(xl, yl, xr2, yr2);
	l3 = Txuh::get_manh_dist(xr, yr, xl2, yl2);
	l4 = Txuh::get_manh_dist(xr, yr, xr2, yr2);

	x0 = xl;
	y0 = yl;
	x2 = xl2;
	y2 = yl2;
	z0 = this->z; // z won't change
	z2 = ms->z;
	d = l1;

	if (l2 < d) {
		x0 = xl;
		y0 = yl;
		x2 = xr2;
		y2 = yr2;
		d = l2;
	}

	if (l3 < d) {
		x0 = xr;
		y0 = yr;
		x2 = xl2;
		y2 = yl2;
		d = l3;
	}

	if (l4 < d) {
		x0 = xr;
		y0 = yr;
		x2 = xr2;
		y2 = yr2;
		d = l4;
	}

	return d;
}

MergingSegment::~MergingSegment() {
	// Auto-generated destructor stub
}

Point<int> MergingSegment::get_left_point() const{
	return pointl;
}

Point<int> MergingSegment::get_right_point() const{
	return pointr;
}

int MergingSegment::get_slope() const{
	return slope;
}

//void MergingSegment::mergeTRR(TRR *trr1, TRR *trr2){
//	// merge two tilted rectangular regions to generate this ms
//	if(!trr1 || !trr2)
//		throw(" Error: MergingSegment::mergeTRR(): merging empty TRRs!");
//
//	Edge temp_e = trr1->merge(trr2);
//	float temp_slope = temp_e.get_slope();
//
//	if(temp_slope!=0 && temp_slope!=1 && temp_slope!=-1){
//		char err_str[];
//		sprintf(err_str, " Error: MergingSegment::mergeTRR(): the slope of the generated edge is %f!", temp_e.get_slope());
//		throw(err_str);
//	}else
//		slope = temp_slope;
//
//
//	set_left_point(temp_e.get_pt_left());
//	set_right_point(temp_e.get_pt_right());
//
//}

void MergingSegment::set_left_point(const Point<int>& pt){
	this->pointl = pt;
	xl = pt.getX();
	yl = pt.getY();
	z = pt.getZ();
}

void MergingSegment::set_right_point(const Point<int>& pt){
	this->pointr = pt;
	xr = pt.getX();
	yr = pt.getY();
	z = pt.getZ();
}

} /* namespace Cxuh */
